package com.wongoing.util.safe.rsa;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;

public class RSAUtil {
	
	private static RSAUtil instance;
	
	public synchronized static RSAUtil getInstance() {
		if(instance==null) {
			instance = new RSAUtil();
		}
		return instance;
	}
	
	/**
	 * 功能说明：RSA加密算法
	 * 修改说明：
	 * @author 吴兆强
	 * @date 2018年6月26日 下午3:27:16
	 * @param encryptedBytes 需加密字符串byte
	 * @param privateKey 密钥
	 * @param keyLength  密钥长度
	 * @param reserveSize 每个存储块的大小
	 * @param cipherAlgorithm 加密填充方式
	 * @return
	 * @throws Exception
	 */
	 public byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {  
	        int keyByteSize = keyLength / 8;  
	        int decryptBlockSize = keyByteSize - reserveSize;  
	        int nBlock = encryptedBytes.length / keyByteSize;  
	        ByteArrayOutputStream outbuf = null;  
	        try {  
	            Cipher cipher = Cipher.getInstance(cipherAlgorithm);  
	            cipher.init(Cipher.DECRYPT_MODE, privateKey);  

	            outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);  
	            for (int offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {  
	                int inputLen = encryptedBytes.length - offset;  
	                if (inputLen > keyByteSize) {  
	                    inputLen = keyByteSize;  
	                }  
	                byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);  
	                outbuf.write(decryptedBlock);  
	            }  
	            outbuf.flush();  
	            return outbuf.toByteArray();  
	        } catch (Exception e) {  
	            throw new Exception("DEENCRYPT ERROR:", e);  
	        } finally {  
	            try{  
	                if(outbuf != null){  
	                    outbuf.close();  
	                }  
	            }catch (Exception e){  
	                outbuf = null;  
	                throw new Exception("CLOSE ByteArrayOutputStream ERROR:", e);  
	            }  
	        }  
	    }  
	 
	 	/**
	 	 * 功能说明：RSA解密算法
	 	 * 修改说明：
	 	 * @author 吴兆强
	 	 * @date 2018年6月26日 下午3:37:59
	 	 * @param plainBytes 需解密byte
	 	 * @param publicKey 公钥
	 	 * @param keyLength 公钥长度
	 	 * @param reserveSize 每个存储块的大小
	 	 * @param cipherAlgorithm 加密填充方式
	 	 * @return
	 	 * @throws Exception
	 	 */
	    public  byte[] encrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {  
	        int keyByteSize = keyLength / 8;  
	        int encryptBlockSize = keyByteSize - reserveSize;  
	        int nBlock = plainBytes.length / encryptBlockSize;  
	        if ((plainBytes.length % encryptBlockSize) != 0) {  
	            nBlock += 1;  
	        }  
	        ByteArrayOutputStream outbuf = null;  
	        try {  
	            Cipher cipher = Cipher.getInstance(cipherAlgorithm);  
	            cipher.init(Cipher.ENCRYPT_MODE, publicKey);  

	            outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);  
	            for (int offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {  
	                int inputLen = plainBytes.length - offset;  
	                if (inputLen > encryptBlockSize) {  
	                    inputLen = encryptBlockSize;  
	                }  
	                byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);  
	                outbuf.write(encryptedBlock);  
	            }  
	            outbuf.flush();  
	            return outbuf.toByteArray();  
	        } catch (Exception e) {  
	            throw new Exception("ENCRYPT ERROR:", e);  
	        } finally {  
	            try{  
	                if(outbuf != null){  
	                    outbuf.close();  
	                }  
	            }catch (Exception e){  
	                outbuf = null;  
	                throw new Exception("CLOSE ByteArrayOutputStream ERROR:", e);  
	            }  
	        }  
	    }  
	    
	    /**
	     * 功能说明：RSA算法获取密钥
	     * 修改说明：
	     * @author 吴兆强
	     * @date 2018年6月26日 下午3:40:36
	     * @param privateKeyPath 密钥路径
	     * @param keyAlgorithm 加密算法填充方式
	     * @return
	     * @throws Exception 
	     */
	    public PrivateKey getPriKey(String privateKeyPath,String keyAlgorithm) throws Exception{  
	        PrivateKey privateKey = null;  
	        InputStream inputStream = null;  
	        try {  
	            inputStream = new FileInputStream(privateKeyPath);  
	            privateKey = getPrivateKey(inputStream,keyAlgorithm);  
	        } catch (Exception e) {  
	        	throw new Exception("LOADED PRIVATE KEY IS ERROR:", e); 
	        } finally {  
	            if (inputStream != null){  
	                try {  
	                    inputStream.close();  
	                }catch (Exception e){  
	                	throw new Exception("CLOSED INPUT STREAM IS ERROR WHEN LOADING PRIVATE KEY：", e); 
	                }  
	            }  
	        }  
	        return privateKey;  
	    }  
	    
	    /**
	     * 功能说明：获取公钥
	     * 修改说明：
	     * @author 吴兆强
	     * @date 2018年6月26日 下午3:49:37
	     * @param publicKeyPath 公钥路径
	     * @param keyAlgorithm 加密算法填充方式
	     * @return
	     * @throws Exception 
	     */
	    public PublicKey getPubKey(String publicKeyPath,String keyAlgorithm) throws Exception{  
	        PublicKey publicKey = null;  
	        InputStream inputStream = null;  
	        try 
	        {
	            System.out.println("getPubkey 1......");
	            inputStream = new FileInputStream(publicKeyPath);  
	            System.out.println("getPubkey 2......");
	            publicKey = getPublicKey(inputStream,keyAlgorithm);  
	            System.out.println("getPubkey 3......");
	        } catch (Exception e) {  
	        	throw new Exception("LOADING PUBLIC KEY ERROR：", e); 
	        } finally {  
	            if (inputStream != null){  
	                try {  
	                    inputStream.close();  
	                }catch (Exception e){  
	                	throw new Exception("CLOSED INPUT STREAM IS ERROR WHEN LOADING PUBLIC KEY：", e); 
	                }  
	            }  
	        }  
	        return publicKey;  
	    }  
	    
	    /**
	     * 功能说明：RSA算法获取公钥
	     * 修改说明：
	     * @author 吴兆强
	     * @date 2018年6月26日 下午3:57:10
	     * @param inputStream 输入流
	     * @param keyAlgorithm 算法填充方式
	     * @return
	     * @throws Exception
	     */
	    public static PublicKey getPublicKey(InputStream inputStream, String keyAlgorithm) throws Exception {  
	        try 
	        {
	            System.out.println("b1.........");
	            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); 
	            System.out.println("b2.........");
	            StringBuilder sb = new StringBuilder();  
	            String readLine = null;
	            System.out.println("b3.........");
	            while ((readLine = br.readLine()) != null) {  
	                if (readLine.charAt(0) == '-') {  
	                    continue;  
	                } else {  
	                    sb.append(readLine);  
	                    sb.append('\r');  
	                }  
	            }  
	            System.out.println("b4.........");
	            X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(Base64.decodeBase64(sb.toString()));  
	            System.out.println("b5.........");
	            KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
	            System.out.println("b6.........");
	            //下行出错  java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: DerInputStream.getLength(): lengthTag=127, too big.
	            PublicKey publicKey = keyFactory.generatePublic(pubX509);  
	            System.out.println("b7.........");
	            return publicKey;  
	        } catch (Exception e) {  
	            e.printStackTrace();
	            System.out.println("b8.........");
	            throw new Exception("READ PUBLIC KEY ERROR:", e);  
	        } finally {  
	            try {  
	                if (inputStream != null) {  
	                    inputStream.close();  
	                }  
	            } catch (IOException e) {  
	                inputStream = null;  
	                throw new Exception("INPUT STREAM CLOSE ERROR:", e);  
	            }  
	        }  
	    }  
	    
	    /**
	     * 功能说明：以流的方式获取密钥
	     * 修改说明：
	     * @author 吴兆强
	     * @date 2018年6月26日 下午3:58:36
	     * @param inputStream
	     * @param keyAlgorithm
	     * @return
	     * @throws Exception
	     */
	     public static PrivateKey getPrivateKey(InputStream inputStream, String keyAlgorithm) throws Exception {  
	            try {  
	                BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));  
	                StringBuilder sb = new StringBuilder();  
	                String readLine = null;  
	                while ((readLine = br.readLine()) != null) {  
	                    if (readLine.charAt(0) == '-') {  
	                        continue;  
	                    } else {  
	                        sb.append(readLine);  
	                        sb.append('\r');  
	                    }  
	                }  
	                System.out.println("hahhah4!"+Base64.decodeBase64(sb.toString()));  
	                PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decodeBase64(sb.toString()));  
	                System.out.println("hahhah5!");  
	                KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);  
	                System.out.println("hahhah6!");  
	                PrivateKey privateKey = keyFactory.generatePrivate(priPKCS8);  
	                System.out.println("hahhah7!");  
	                return privateKey;  
	            } catch (Exception e) {  
	                throw new Exception("READ PRIVATE KEY ERROR:" ,e);  
	            }  finally {  
	                try {  
	                    if (inputStream != null) {  
	                        inputStream.close();  
	                    }  
	                } catch (IOException e) {  
	                    inputStream = null;  
	                    throw new Exception("INPUT STREAM CLOSE ERROR:", e);  
	                }  
	            }  
	        }  
}
